#include "depui/depui.h"
#include "detk/file.h"
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#ifndef MXMODULE_DIRENT
#   include <dir.h>
#   include <dirent.h>
#endif

#define MXMODULE_EDITTEXT
#define MXMODULE_LIST
#define MXMODULE_MATCH
#define MXMODULE_BASENAME
#define MXMODULE_PATHFIX

/**todo Borland free compiler does something wierd.  PATH_MAX is
   included in limits.h so I include it, but for some reason PATH_MAX
   doesnt get defined.  So I'll just make a reasonable guess.
*/

#if !defined(PATH_MAX)
#   define PATH_MAX 512
#endif

static void mx__filesel_geometry(MX_FILESEL * sel)
{
	int lh;
	const int lw = (mx_w(sel) - 1) / 2;

	MXDEBUG_INVARIANT(MXOBJ(sel));

	mx_defaultrect(&sel->_ok, 0);
	mx_layout(&sel->_ok, (MX_LAYOUT_X2 | MX_LAYOUT_Y2), sel, 0, 0);
	mx_geometry(&sel->_ok);

	mx_defaultrect(&sel->_file, 0);
	mx_resize(&sel->_file, mx_w(sel), MXDEFAULT);
	mx_geometry(&sel->_file);

	mx_layout(&sel->_file, (MX_LAYOUT_X2 | MX_LAYOUT_TOP), &sel->_ok, 0, 1);
	lh = mx_y(&sel->_file) - 2;

	mx_position(&sel->_dirs, 0, 0, lw, lh);
	mx_geometry(&sel->_dirs);

	mx_position(&sel->_files, lw + 2, 0, lw + 2, lh);
	mx_geometry(&sel->_files);
}

static void mx__filesel_splitpattern(MX_FILESEL * sel, char *path, char *pattern)
{
	char *end;

	MXDEBUG_INVARIANT(MXOBJ(sel));

	mx_edittext_zeroterminate(&sel->_file);

	strncpy(path, mx_text(&sel->_file, 0), PATH_MAX + FILENAME_MAX);
	path[PATH_MAX + FILENAME_MAX] = '\0';

	end = (char *) mx_basename(path);

	strncpy(pattern, end, FILENAME_MAX);
	pattern[FILENAME_MAX] = 0;

	*end = '\0';

	mx_path_fix(path);
}

/** !Refresh file selector contents
This function refreshes the directory and file lists of the file selector. */
void mx_filesel_refresh(MX_FILESEL * sel)
{
	DIR *dir;
	struct dirent *entry;
	char *pattern;
	char *path;

		/**todo Can we add a list of drive letters to the directory list for DOS? */

#ifdef __DJGPP__
	const unsigned short old_djstat_flags = _djstat_flags;

	_djstat_flags |= _STAT_INODE | _STAT_EXEC_EXT | _STAT_EXEC_MAGIC | _STAT_DIRSIZE | _STAT_ROOT_TIME | _STAT_WRITEBIT;
#endif

	MXDEBUG_INVARIANT(MXOBJ(sel));

	mx_disable(&sel->_ok, false);
	mx_list_empty(&sel->_files);
	mx_list_empty(&sel->_dirs);
	mx_list_append(&sel->_dirs, "..", -1, 0, 0);

	pattern = mx_malloc(FILENAME_MAX + 1);
	path = mx_malloc(PATH_MAX + FILENAME_MAX + 1);
	assert(pattern);
	assert(path);

	strncpy(path, mx_text(&sel->_file, 0), PATH_MAX + FILENAME_MAX);
	path[PATH_MAX + FILENAME_MAX] = '\0';

	strcpy(pattern, "*");

	if ((strchr(path, '*')) || (strchr(path, '?')))
		mx__filesel_splitpattern(sel, path, pattern);

	dir = opendir(path);

	while ((dir) && ((entry = readdir(dir)) != 0)) {
		struct stat sbuf;
		MX_LIST *thelist = 0;
		char *text;
		char *statpath;
		int namlen;

		if (entry->d_name[0] == '.')
			continue;

		statpath = mx_malloc(PATH_MAX + FILENAME_MAX + 1);
		namlen = strlen(entry->d_name);
		text = mx_malloc((unsigned) namlen + 1);
		assert(statpath);
		assert(text);

		strncpy(text, entry->d_name, (unsigned) namlen);
		text[namlen] = 0;
		strcpy(statpath, path);
		strcat(statpath, text);

		mx_path_fix(statpath);

		if (!stat(statpath, &sbuf)) {

#ifdef __TURBOC__
#   define S_ISDIR(mode) ((mode) & S_IFDIR)
#endif
			if (S_ISDIR(sbuf.st_mode))
				thelist = &sel->_dirs;

			else if (mx_filename_match(pattern, text))
				thelist = &sel->_files;
		}

		if (thelist)
			mx_list_append(thelist, text, namlen, mx_free, 0);
		else
			mx_free(text);

		mx_free(statpath);
	}

	strcat(path, pattern);
	mx_text_set(&sel->_file, path, -1, mx_free);
	mx_edittext_cursorpos(&sel->_file, 0x7fff);

	mx_free(pattern);
	closedir(dir);

#ifdef __DJGPP__
	_djstat_flags = old_djstat_flags;
#endif
}

static void mx__filesel_dir(MX_FILESEL * sel, const char *text, int len)
{
	int pathlen;
	char *pattern;
	char *path;

	if (!text)
		return;

	MXDEBUG_INVARIANT(MXOBJ(sel));

	pattern = mx_malloc(FILENAME_MAX + 1);
	path = mx_malloc(PATH_MAX + FILENAME_MAX + 1);
	assert(pattern);
	assert(path);
	mx__filesel_splitpattern(sel, path, pattern);

	pathlen = strlen(path);
	if (len < 0)
		len = strlen(text);

	strncpy(&path[pathlen], text, (unsigned) len);
	path[pathlen + len] = '/';
	path[pathlen + len + 1] = '\0';

	mx_path_fix(path);
	strcat(path, pattern);

	mx_filesel_path(sel, path, -1);

	mx_filesel_refresh(sel);
	mx__filesel_geometry(sel);

	mx_disable(&sel->_ok, true);

	mx_dirty(&sel->_ok, true);
	mx_dirty(&sel->_file, true);
	mx_dirty(&sel->_dirs, true);
	mx_dirty(&sel->_files, true);

	mx_free(path);
	mx_free(pattern);
}

void mx_filesel_handler(MX_WIN * win)
{
	MX_FILESEL *sel = (MX_FILESEL *) win;

	MXDEBUG_INVARIANT(MXOBJ(win));
	MXDEBUG_INVARIANT(MXOBJ(mx.obj));

	/* Handle selmode events */
	if (mx_eventmatch(MX_GEOMETRY, win)) {
		mx_win_handler(win);
		mx__filesel_geometry(sel);
		return;

	} else if (mx_eventmatch(MX_DEFAULTRECT, win)) {
		MX_RECT *rrect = mx_defaultrect_data();

		rrect->x2 = rrect->x1 + 280;
		rrect->y2 = rrect->y1 + 160;
		return;

	/**module Pressing enter/return in the filename area causes the directory and
    file lists to be refreshed with the new pattern. */
	} else if (mx_eventmatch(MX_KEY, &sel->_file)) {

		const MX_KEY_INFO *info = mx_key_info();

		if (info->ascii == '\r') {
			int len = 0;
			const char *text;

			mx_edittext_zeroterminate(&sel->_file);

			text = mx_text(&sel->_file, &len);

			mx_filesel_path(sel, text, len);

			mx_filesel_refresh(sel);
			mx__filesel_geometry(sel);

			mx_disable(&sel->_ok, true);

			mx_dirty(&sel->_ok, true);
			mx_dirty(&sel->_file, true);
			mx_dirty(&sel->_dirs, true);
			mx_dirty(&sel->_files, true);
			return;

		} else if (info->ascii == 0x1b) {
			mx_win_dirty(sel);
			mx_delete(sel);

		} else {
			mx_disable(&sel->_ok, false);
			mx_dirty(&sel->_ok, true);
		}

		/**module Clicking on the directory list causes the directory and file
       lists to be refreshed. */
	} else if (mx_eventmatch(MX_LIST_CHANGED, &sel->_dirs)) {
		const MX_LISTELEM *elem;

		mx_win_handler(win);

		elem = mx_list_selected(&sel->_dirs, (MX_LISTELEM *) 0);
		if (elem) {
			int len = 0;
			const char *text = mx_text(elem, &len);

			mx__filesel_dir(sel, text, len);
		}
		return;

		/**module Clicking on the file list selects a file. */
	} else if (mx_eventmatch(MX_LIST_CHANGED, &sel->_files)) {
		mx_win_handler(win);
		mx_disable(&sel->_ok, false);
		mx_dirty(&sel->_ok, true);
		return;

		/* Handle selection events */
	} else if ((mx.event == MX_SELECT) && (mx.data)) {

		/* Button ok selected */
		if (MXOBJ(mx.obj) == MXOBJ(&sel->_ok)) {

			/* Put the selected filename in the current place */
			MX_LISTELEM *selected = mx_list_selected(&sel->_files, (MX_LISTELEM *) 0);

			if (selected) {
				int len = 0;
				const char *text = mx_text(selected, &len);

				if ((text) && (len)) {
					char *pattern;
					char *path;

					pattern = mx_malloc(FILENAME_MAX + 1);
					path = mx_malloc(PATH_MAX + FILENAME_MAX + 1);
					assert(pattern);
					assert(path);
					mx__filesel_splitpattern(sel, path, pattern);

					if (len < 0)
						strcat(path, text);
					else
						strncat(path, text, (unsigned) len + 1);

					mx_text_set(&sel->_file, path, -1, mx_free);
					mx_edittext_cursorpos(&sel->_file, 0x7fff);

					mx_free(pattern);
				}
			}

												/**module Pressing OK causes a MX_FILESEL_OK event to be sent to the
            parent window.  WHen halding this event the parent window can call
            mx_filesel_info() to get the selected filename.  The file selector
            widnow is destroyed afetr the MX_FILESEL_OK event. */
			mx_inform(MX_FILESEL_OK, sel, sizeof(sel));
			mx_delete(sel);
			return;
		}
	}
	mx_win_handler(win);
}

/** !Create file selector
This function creates a file selector object with a given handler and id number. */
MX_FILESEL *mx_fileselwin(MX_FILESEL * sel, size_t size, MX_HANDLER handler, int theid)
{
	MXMINSIZE(size, MX_FILESEL);

	if (!handler)
		handler = mx_filesel_handler;

	sel = (MX_FILESEL *) mx_win(sel, size, handler, theid);
	if (sel) {
		MXDEBUG_ALLOCTAG(sel);
		MXDEBUG_ATOMNAME(sel, "filesel");
		sel->_existing = true;
		mx_text_set(sel, "Choose file...", -1, 0);

		mx_button(&sel->_ok, 0, sel, 0);
		mx_text_set(&sel->_ok, "ok", -1, 0);
		MXDEBUG_ATOMNAME(&sel->_ok, "filesel_ok");

		mx_list(&sel->_dirs, 0, sel, 0);
/**fix          mx_text_set(&sel->_dirs, "dir", -1, 0); */
		MXDEBUG_ATOMNAME(&sel->_dirs, "filesel_dir");

		mx_list(&sel->_files, 0, sel, 0);
/**fix          mx_text_set(&sel->_files, "file", -1, 0); */
		MXDEBUG_ATOMNAME(&sel->_files, "filesel_files");

		mx_edittext(&sel->_file, 0, sel, 0);
		MXDEBUG_ATOMNAME(&sel->_file, "filesel_file");

		mx_text_set(&sel->_file, "./*", -1, 0);
		mx_edittext_cursorpos(&sel->_file, 0x7fff);
		mx_focus_default(&sel->_file);
	}

	MXDEBUG_INVARIANT(MXOBJ(sel));
	return sel;
}

/** !Set file selector path and pattern
This function sets the file selector path and pattern.  It does not 
refresh the directory and file lists. */
void mx_filesel_path(MX_FILESEL * sel, const char *path, int len)
{
	MXDEBUG_INVARIANT(MXOBJ(sel));

	if (!path)
		path = "";

				/** If the len parameter is less than 0 then the path is assumed to be
        zero terminated. */
	if (len < 0)
		len = strlen(path);

	mx_text_set(&sel->_file, path, len, 0);
	mx_edittext_cursorpos(&sel->_file, 0x7fff);
}

/** !Allow a file selector to accept an existing (the default) or non-existing file */
void mx_filesel_existing(MX_FILESEL * sel, unsigned exist)
{
	sel->_existing = exist;
}

/** !File selector information
When a file has been selected with a file selector a MX_FILESEL_OK
event is sent to the parent window.  Then this function can be called to 
determine the filename selected. */
const char *mx_filesel_info(const int idnum)
{
	MX_FILESEL *sel;

/** This function should only be called while handling a MX_FILESEL_OK event. */
	assert(mx.event == MX_FILESEL_OK);

	if (mx.event != MX_FILESEL_OK)
		return 0;

	sel = (MX_FILESEL *) mx.data;
	MXDEBUG_INVARIANT(MXOBJ(sel));

/** The specified id number must match the one of the file selector. */
	if ((sel) && (MXID(sel) == idnum)) {

								/** The returned filename is zero terminated. */
		mx_edittext_zeroterminate(&sel->_file);
		return mx_text(&sel->_file, 0);
	}
	return 0;
}
